package config

import (
	"encoding/json"
	"fmt"
	"github.com/fsnotify/fsnotify"
	"github.com/nacos-group/nacos-sdk-go/clients"
	"github.com/nacos-group/nacos-sdk-go/common/constant"
	"github.com/nacos-group/nacos-sdk-go/vo"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"strings"
)

// AnalyzeConfig 解析配置文件，并将配置信息映射到传入的参数中
//
//	   该方法需要再corbra解析完命令后再调用，这样就可以将命令行的参数映射到options里面
//				@Description:
//				     该方法是先读取一个配置文件的设置，从中获取到真正业务的配置文件数据，然后再读取对应的配置文件的方式
//			      整体配置文件的来源有三种方式：
//			          从当前路径config.yaml文件中读取(默认)、从环境变量中读取、从命令行参数中读取
//			          三种读取方式优先级依次上升
//			       该方法支持从本地配置文件读取(ini,json,yaml文件)和从配置中心nacos中读取的方式，通过总配置的type字段区分
//
//				@param configTypes ：需要映射的类型集合，这里要传入指针，否则无法映射成功
//		           需要注意的是，如果配置对象有继承关系，需要将父类和子类分别传入，否则无法解析成功
//			 @return  返回读取文件的viper对象，以供用户自由获取
func AnalyzeConfig(options *ConfigOptions, configTypes ...interface{}) (viper *viper.Viper) {

	// 从本地配置文件config.yaml中读取整体配置信息
	var err error
	if options.Type == "init" {
		// 这里的错误不进行返回，因为有可能用户直接将config.yaml作为业务配置文件
		viper, err = ReadYamlConfig(options.NativePath, options.NativeName)
		if err == nil {
			_ = MappingConfigToStruct(viper, options)
		}
	}
	// 校验主配置文件
	err = options.Validate()
	if err != nil {
		panic(err)
	}
	mainConfigType := strings.ToLower(options.Type)
	switch mainConfigType {
	case "native", "init":
		// 从本地配置文件获取
		_, err = readConfigFromNativeFile(options, configTypes...)
		if err != nil {
			panic(err)
		}
	case "nacos":
		err = readConfigFromNacos(options, configTypes...)
		if err != nil {
			panic(err)
		}
	default:
		return
	}
	return
}

func readConfigFromNacos(option *ConfigOptions, configTypes ...interface{}) error {
	//从nacos中读取配置信息
	sc := []constant.ServerConfig{
		{
			IpAddr: option.Nacos.Host,
			Port:   uint64(option.Nacos.Port),
		},
	}

	cc := constant.ClientConfig{
		// 如果需要支持多namespace，我们可以场景多个client,它们有不同的NamespaceId
		NamespaceId:         option.Nacos.Namespace,
		TimeoutMs:           5000,
		NotLoadCacheAtStart: true,
		LogDir:              "tmp/nacos/log",
		CacheDir:            "tmp/nacos/cache",
		LogLevel:            "debug",
	}

	configClient, err := clients.CreateConfigClient(map[string]interface{}{
		"serverConfigs": sc,
		"clientConfig":  cc,
	})

	if err != nil {
		zap.S().Errorf("create config client failed.error=%v", err.Error())
		return err
	}
	content, err := configClient.GetConfig(vo.ConfigParam{
		DataId: option.Nacos.DataId,
		Group:  option.Nacos.Group})

	if err != nil {
		zap.S().Errorf("get config failed.error=%v", err.Error())
		return err
	}
	if len(configTypes) == 0 {
		return nil
	}
	for _, configType := range configTypes {
		err = unmarshalContentToStruct(content, configType)
		if err != nil {
			return err
		}
	}

	// 检测变化
	err = configClient.ListenConfig(vo.ConfigParam{
		DataId: option.Nacos.DataId,
		Group:  option.Nacos.Group,
		OnChange: func(namespace, group, dataId, data string) {
			zap.S().Info("nacos config is changed")
			for _, configType := range configTypes {
				err = unmarshalContentToStruct(content, configType)
				if err != nil {
					zap.S().Errorf("unmarshal content to struct failed.error=%v", err.Error())
				}
			}
		},
	})
	if err != nil {
		return err
	}
	return nil
}
func unmarshalContentToStruct(content string, structType interface{}) error {
	return json.Unmarshal([]byte(content), structType)
}

// 从本地配置文件获取配置数据
func readConfigFromNativeFile(option *ConfigOptions, configTypes ...interface{}) (viper *viper.Viper, err error) {
	nativeType := strings.ToLower(option.NativeType)
	switch nativeType {
	case "ini":
		viper, err = ReadIniConfig(option.NativePath, option.NativeName)
		if err != nil {
			return viper, err
		}
	case "json":
		viper, err = ReadJsonConfig(option.NativePath, option.NativeName)
		if err != nil {
			return viper, err
		}
	case "yaml":
		viper, err = ReadYamlConfig(option.NativePath, option.NativeName)
		if err != nil {
			return viper, err
		}
	default:
		return viper, fmt.Errorf("not supported nativeType: %v", option.NativeType)
	}
	if len(configTypes) == 0 {
		return viper, nil
	}
	for _, configType := range configTypes {
		err := MappingConfigToStruct(viper, configType)
		if err != nil {
			return viper, err
		}
	}
	// 给wiper配置监听
	viper.OnConfigChange(func(e fsnotify.Event) {
		zap.S().Info("config file changed")
		// 监听到变化后直接更新到配置中
		for _, configType := range configTypes {
			err := MappingConfigToStruct(viper, configType)
			if err != nil {
				zap.S().Errorf("unmarshal native config file failed.error=%v", err)
			}
		}
	})
	viper.WatchConfig()
	return viper, nil
}
